﻿using Manox.Libs.Foundation;
using Manox.Libs.Networking;
using System;
using System.Collections.Generic;

namespace Manox.Libs.ODB
{
    /// <summary>
    /// Object database list base.
    /// </summary>
    public abstract class ObjectDatabaseListBase : ObjectDatabaseItem
    {
        /// <summary>Count.</summary>
        public abstract Int32 Count { get; }

        /// <summary>Collection changed.</summary>
        public event ObjectDatabaseListChangedEventArgsCallback CollectionChanged;

        /// <summary>
        /// Call data changed event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="eventArgs"></param>
        public virtual void CallCollectionChangedEvent(object sender, ObjectDatabaseListChangedEventArgs eventArgs)
        {
            if (this.CollectionChanged != null)
                this.CollectionChanged(sender, eventArgs);
        }

        /// <summary>
        /// Clear.
        /// </summary>
        /// <param name="sender"></param>
        public abstract void Clear(object sender);

        /// <summary>
        /// Method adds new item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="newItem"></param>
        public abstract void Add(object sender, object newItem);

        /// <summary>
        /// Method removes item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="item"></param>
        public abstract void Remove(object sender, object item);

        /// <summary>
        /// Set item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public abstract void SetItemBase(object sender, int index, object value);

        /// <summary>
        /// Get item.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public abstract object GetItemBase(int index);
    }

    /// <summary>
    /// Object data changed callback.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    public delegate void ObjectDatabaseListChangedEventArgsCallback(object sender, ObjectDatabaseListChangedEventArgs eventArgs);

    /// <summary>
    /// Object database list.
    /// </summary>
    public class ObjectDatabaseList<T> : ObjectDatabaseListBase
    {
        /// <summary>List items.</summary>
        private List<T> Items;

        /// <summary>Count.</summary>
        public override Int32 Count { get { return this.Items.Count; } }
        ///// <summary>
        ///// Indexer.
        ///// </summary>
        ///// <param name="index"></param>
        ///// <returns></returns>
        //public T this[Int32 index]
        //{
        //    get { return this.Items[index]; }
        //    set { this.Items[index] = value; }
        //}

        /// <summary>
        /// Constructor.
        /// </summary>
        public ObjectDatabaseList()
        {
            this.Items = new List<T>();
        }

        /// <summary>
        /// Clear.
        /// </summary>
        /// <param name="sender"></param>
        public override void Clear(object sender)
        {
            this.Items.Clear();

            ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
            eventArgs.DatabaseObjectId = this.Id;
            eventArgs.EventType = ListChangedEventType.Clear;            

            this.CallDataChangedEvent(sender, eventArgs);
            this.CallCollectionChangedEvent(sender, eventArgs);
        }

        /// <summary>
        /// Method adds new item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="newItem"></param>
        public override void Add(object sender, object newItem)
        {
            this.Add(sender, (T)newItem);
        }

        /// <summary>
        /// Method adds new item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="newItem"></param>
        public void Add(object sender, T newItem)
        {
            this.Items.Add(newItem);

            ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
            eventArgs.DatabaseObjectId = this.Id;
            eventArgs.EventType = ListChangedEventType.ItemAdded;
            eventArgs.AffectedItem = newItem;

            this.CallDataChangedEvent(sender, eventArgs);
            this.CallCollectionChangedEvent(sender, eventArgs);
        }

        /// <summary>
        /// Method removes item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="item"></param>
        public override void Remove(object sender, object item)
        {
            this.Remove(sender, (T)item);
        }

        /// <summary>
        /// Method removes item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="item"></param>
        public void Remove(object sender, T item)
        {
            if (this.Items.Contains(item))
            {
                this.Items.Remove(item);

                ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
                eventArgs.DatabaseObjectId = this.Id;
                eventArgs.EventType = ListChangedEventType.ItemRemoved;
                eventArgs.AffectedItem = item;

                this.CallDataChangedEvent(sender, eventArgs);
                this.CallCollectionChangedEvent(sender, eventArgs);
            }
        }

        /// <summary>
        /// Set item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public override void SetItemBase(object sender, int index, object value)
        {
            this.Items[index] = (T)value;

            ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
            eventArgs.DatabaseObjectId = this.Id;
            eventArgs.EventType = ListChangedEventType.ItemChanged;
            eventArgs.AffectedItem = this.Items[index];

            this.CallDataChangedEvent(sender, eventArgs);
            this.CallCollectionChangedEvent(sender, eventArgs);
        }

        /// <summary>
        /// Get item.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public override object GetItemBase(int index)
        {
            return this.Items[index];
        }

        /// <summary>
        /// Set item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="index"></param>
        /// <param name="value"></param>
        public void SetItem(object sender, int index, T value)
        {
            this.Items[index] = value;

            ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
            eventArgs.DatabaseObjectId = this.Id;
            eventArgs.EventType = ListChangedEventType.ItemChanged;
            eventArgs.AffectedItem = this.Items[index];

            this.CallDataChangedEvent(sender, eventArgs);
            this.CallCollectionChangedEvent(sender, eventArgs);
        }

        /// <summary>
        /// Get item.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T GetItem(int index)
        {
            return this.Items[index];
        }

        /// <summary>
        /// Initialize in ODB.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="odbManager"></param>
        public override void InitializeInODB(object sender, ObjectDatabaseManager odbManager)
        {            
            for (int i = 0; i < this.Count; i++)
                if (this.Items[i] is ObjectDatabaseItem)
                {
                    ObjectDatabaseItem item = this.Items[i] as ObjectDatabaseItem;

                    if (item.Id == Guid.Empty)
                        odbManager.AddObject(sender, item);
                }
        }

        /// <summary>
        /// Initializing EventArgs.
        /// </summary>
        /// <returns></returns>
        public override List<ObjectDatabaseEventArgs> InitializingEventArgs()
        {
            List<ObjectDatabaseEventArgs> output = new List<ObjectDatabaseEventArgs>();

            for (int i = 0; i < this.Count; i++)
            {
                ObjectDatabaseListChangedEventArgs eventArgs = new ObjectDatabaseListChangedEventArgs();
                eventArgs.DatabaseObjectId = this.Id;
                eventArgs.EventType = ListChangedEventType.ItemAdded;
                eventArgs.AffectedItem = this.Items[i];

                output.Add(eventArgs);
            }

            return output;
        }
    }

    /// <summary>
    /// List changed event type.
    /// </summary>
    public enum ListChangedEventType : int
    {
        Clear,
        ItemAdded,
        ItemRemoved,
        ItemChanged,
    }

    /// <summary>
    /// Object database Object property value changed.
    /// </summary>
    public class ObjectDatabaseListChangedEventArgs : ObjectDatabaseEventArgs
    {
        /// <summary>Database Object id.</summary>
        public Guid DatabaseObjectId;
        /// <summary>Event type.</summary>
        public ListChangedEventType EventType;
        /// <summary>Affected item.</summary>
        public object AffectedItem;


#if UNITY_NETWORK || STEAM_NETWORK
        /// <summary>
        /// Serialize.
        /// </summary>
        /// <param name="writer"></param>
        public override void Serialize(SerializeWriter writer)
        {
            base.Serialize(writer);

            writer.Write(this.DatabaseObjectId);
            writer.Write((int)this.EventType);
            writer.WriteObject(this.AffectedItem);
        }

        /// <summary>
        /// Deserialize.
        /// </summary>
        /// <param name="reader"></param>
        public override void Deserialize(SerializeReader reader)
        {
            base.Deserialize(reader);

            this.DatabaseObjectId = reader.ReadGuid();
            this.EventType = (ListChangedEventType)reader.ReadInt32();
            this.AffectedItem = reader.ReadObject();
        }
#endif             
    }
}
